//go:build sql_integration

package requestmgr

import (
	"context"
	"testing"

	imageDS "github.com/stackrox/rox/central/image/datastore"
	imagePostgresV2 "github.com/stackrox/rox/central/image/datastore/store/v2/postgres"
	imageV2DS "github.com/stackrox/rox/central/imagev2/datastore"
	imageV2Postgres "github.com/stackrox/rox/central/imagev2/datastore/store/postgres"
	"github.com/stackrox/rox/central/ranking"
	mockRisks "github.com/stackrox/rox/central/risk/datastore/mocks"
	views "github.com/stackrox/rox/central/views/imagecve"
	"github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/cache"
	vulnReqCache "github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/cache"
	vulnReqDatastore "github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/datastore"
	"github.com/stackrox/rox/generated/storage"
	"github.com/stackrox/rox/pkg/concurrency"
	"github.com/stackrox/rox/pkg/features"
	"github.com/stackrox/rox/pkg/fixtures"
	imageUtils "github.com/stackrox/rox/pkg/images/utils"
	"github.com/stackrox/rox/pkg/postgres/pgtest"
	"github.com/stackrox/rox/pkg/sac"
	"github.com/stretchr/testify/suite"
	"go.uber.org/mock/gomock"
	"golang.org/x/sync/semaphore"
)

type managerImplTestFlatData struct {
	suite.Suite

	ctx    context.Context
	testDB *pgtest.TestPostgres

	manager          *managerImpl
	vulnReqDatastore vulnReqDatastore.DataStore
}

func TestVulnRequestManagerFlatModel(t *testing.T) {
	suite.Run(t, new(managerImplTestFlatData))
}

func (m *managerImplTestFlatData) SetupSuite() {
	mockCtrl := gomock.NewController(m.T())
	m.ctx = sac.WithAllAccess(context.Background())
	m.testDB = pgtest.ForT(m.T())
	mockRisk := mockRisks.NewMockDataStore(mockCtrl)
	cveView := views.NewCVEView(m.testDB.DB)

	// setup datastores
	var err error
	m.vulnReqDatastore, err = vulnReqDatastore.GetTestPostgresDataStore(m.T(), m.testDB, cache.New(), cache.New())
	m.Require().NoError(err)
	var imageStore imageDS.DataStore
	var imageV2Store imageV2DS.DataStore
	if features.FlattenImageData.Enabled() {
		imageV2Store = imageV2DS.NewWithPostgres(
			imageV2Postgres.New(m.testDB.DB, false, concurrency.NewKeyFence()),
			mockRisk,
			ranking.ImageRanker(),
			ranking.ComponentRanker(),
		)
	} else {
		imageStore = imageDS.NewWithPostgres(
			imagePostgresV2.New(m.testDB.DB, false, concurrency.NewKeyFence()),
			mockRisk,
			ranking.ImageRanker(),
			ranking.ComponentRanker(),
		)
	}
	m.manager = &managerImpl{
		images:          imageStore,
		imagesV2:        imageV2Store,
		vulnReqs:        m.vulnReqDatastore,
		pendingReqCache: vulnReqCache.New(),
		stopper:         concurrency.NewStopper(),
		upsertSem:       semaphore.NewWeighted(1),
		imageCVEView:    cveView,
	}
	// Add Test Data to DataStores
	testImage := &storage.Image{
		Id: "sha1",
		Name: &storage.ImageName{
			Registry: "reg1",
			Remote:   "img1",
			Tag:      "tag1",
			FullName: "reg1/img1:tag1",
		},
		SetCves: &storage.Image_Cves{
			Cves: 3,
		},
		Scan: &storage.ImageScan{
			Components: []*storage.EmbeddedImageScanComponent{
				{
					Name:    "comp1",
					Version: "0.9",
					Vulns: []*storage.EmbeddedVulnerability{
						{
							Cve: "cve-2018-1",
							SetFixedBy: &storage.EmbeddedVulnerability_FixedBy{
								FixedBy: "1.1",
							},
							Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY,
						},
					},
				},
				{
					Name:    "comp2",
					Version: "1.1",
					Vulns: []*storage.EmbeddedVulnerability{
						{
							Cve: "cve-2018-1",
							SetFixedBy: &storage.EmbeddedVulnerability_FixedBy{
								FixedBy: "1.5",
							},
							Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY,
						},
						{
							Cve: "cve-2017-1",
							SetFixedBy: &storage.EmbeddedVulnerability_FixedBy{
								FixedBy: "1.5",
							},
							Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY,
						},
					},
				},
				{
					Name:     "comp3",
					Version:  "1.0",
					Source:   storage.SourceType_JAVA,
					Location: "p/q/r",
					HasLayerIndex: &storage.EmbeddedImageScanComponent_LayerIndex{
						LayerIndex: 10,
					},
					Vulns: []*storage.EmbeddedVulnerability{
						{
							Cve:      "cve-2019-1",
							Cvss:     4,
							Severity: storage.VulnerabilitySeverity_MODERATE_VULNERABILITY_SEVERITY,
						},
						{
							Cve:      "cve-2019-2",
							Cvss:     3,
							Severity: storage.VulnerabilitySeverity_LOW_VULNERABILITY_SEVERITY,
						},
					},
				},
			},
		},
	}

	if features.FlattenImageData.Enabled() {
		err = imageV2Store.UpsertImage(m.ctx, imageUtils.ConvertToV2(testImage))
	} else {
		err = imageStore.UpsertImage(m.ctx, testImage)
	}
	m.NoError(err)

}

func (m *managerImplTestFlatData) TestCreateDeferralWithAnyFixableCVEs() {
	req := fixtures.GetImageScopeDeferralRequest("reg1", "img1", ".*", "cve-2018-1")
	req.Entities = &storage.VulnerabilityRequest_Cves{
		Cves: &storage.VulnerabilityRequest_CVEs{
			Cves: []string{"cve-2018-1", "cve-2019-1", "cve-2019-2"},
		},
	}
	req.Status = storage.RequestStatus_APPROVED
	req.Req = &storage.VulnerabilityRequest_DeferralReq{
		DeferralReq: &storage.DeferralRequest{
			Expiry: &storage.RequestExpiry{
				Expiry: &storage.RequestExpiry_ExpiresWhenFixed{
					ExpiresWhenFixed: true,
				},
				ExpiryType: storage.RequestExpiry_ANY_CVE_FIXABLE,
			},
		},
	}
	m.NoError(m.vulnReqDatastore.AddRequest(m.ctx, req))
	fixableDeferrals, err := m.manager.getFixableDeferrals()
	m.Require().NoError(err)
	m.Require().NotEmpty(fixableDeferrals)
	requests := []string{req.GetId()}
	m.NoError(m.vulnReqDatastore.RemoveRequestsInternal(m.ctx, requests))
}

func (m *managerImplTestFlatData) TestCreateDeferralWithAllFixableCVEs() {
	req := fixtures.GetImageScopeDeferralRequest("reg1", "img1", ".*", "cve-2018-1", "2017-1")
	req.Entities = &storage.VulnerabilityRequest_Cves{
		Cves: &storage.VulnerabilityRequest_CVEs{
			Cves: []string{"cve-2018-1", "cve-2017-1"},
		},
	}
	cves := req.GetCves().GetCves()
	m.Require().NotEmpty(cves)
	req.Status = storage.RequestStatus_APPROVED
	req.Req = &storage.VulnerabilityRequest_DeferralReq{
		DeferralReq: &storage.DeferralRequest{
			Expiry: &storage.RequestExpiry{
				Expiry: &storage.RequestExpiry_ExpiresWhenFixed{
					ExpiresWhenFixed: true,
				},
				ExpiryType: storage.RequestExpiry_ALL_CVE_FIXABLE,
			},
		},
	}
	m.NoError(m.vulnReqDatastore.AddRequest(m.ctx, req))
	fixableDeferrals, err := m.manager.getFixableDeferrals()
	m.Require().NoError(err)
	m.Require().NotEmpty(fixableDeferrals)
	requests := []string{req.GetId()}
	m.NoError(m.vulnReqDatastore.RemoveRequestsInternal(m.ctx, requests))
}
